package jmind.core.geo;

import java.text.DecimalFormat;
import java.text.NumberFormat;

import jmind.base.util.DataUtil;

/**
 * 将物理平面坐标转换为正方形坐标，然后按照三虚拟坐标系分别划分区块。<br/>
 * 使用步骤：<br/>
 * 1、getLocation1，getLocation2，getLocation3，本别计算坐标在三系中的区块值并存储<br/>
 * 2、根据区块搜索getLocationArea，从三个坐标系中，分别计算最小区域，然后选择最合适坐标系，根据location区块编号搜索。
 */
public class Location2DUtil {
    private double minLon;
    private double minLat;
    private double maxLon;
    private double maxLat;
    private int step;
    private long vitualSize = 10000000000L;

    private static Location2DUtil instance;

    public Location2DUtil(double minLon, double minLat, double maxLon, double maxLat, int step) {
        this.minLon = minLon;
        this.minLat = minLat;
        this.maxLon = maxLon;
        this.maxLat = maxLat;
        this.step = step;
    }

    /**
     * 取得物理坐标点在第一坐标系的区块标号
     * 
     * @param lon
     * @param lat
     * @return
     */
    public long getLocation1(double lon, double lat) {
        long lonL = (long) (vitualSize * (lon - minLon) / (maxLon - minLon));
        long latL = (long) (vitualSize * (lat - minLat) / (maxLat - minLat));
        StringBuffer sb = new StringBuffer();
        for (int i = 0; i < step; i++) {
            long s = (long) (vitualSize / Math.pow(10, i + 1));
            sb.append(lonL / s);
            sb.append(latL / s);
            lonL = lonL % s;
            latL = latL % s;
        }
        return DataUtil.toLong(sb.toString());
    }

    /**
     * 取得物理坐标点在第二坐标系的区块标号
     * 
     * @param lon
     * @param lat
     * @return
     */
    public long getLocation2(double lon, double lat) {
        long lonL = (long) (vitualSize * (lon - minLon) / (maxLon - minLon));
        long latL = (long) (vitualSize * (lat - minLat) / (maxLat - minLat));
        lonL = lonL - vitualSize / 30;
        if (lonL < 0) {
            lonL += vitualSize;
        }
        latL = latL - vitualSize / 30;
        if (latL < 0) {
            latL += vitualSize;
        }
        StringBuffer sb = new StringBuffer();
        for (int i = 0; i < step; i++) {
            long s = (long) (vitualSize / Math.pow(10, i + 1));
            sb.append(lonL / s);
            sb.append(latL / s);
            lonL = lonL % s;
            latL = latL % s;
        }
        return DataUtil.toLong(sb.toString());
    }

    /**
     * 取得物理坐标点在第三坐标系的区块标号
     * 
     * @param lon
     * @param lat
     * @return
     */
    public long getLocation3(double lon, double lat) {
        long lonL = (long) (vitualSize * (lon - minLon) / (maxLon - minLon));
        long latL = (long) (vitualSize * (lat - minLat) / (maxLat - minLat));
        lonL = lonL - vitualSize / 15;
        if (lonL < 0) {
            lonL += vitualSize;
        }
        latL = latL - vitualSize / 15;
        if (latL < 0) {
            latL += vitualSize;
        }
        StringBuffer sb = new StringBuffer();
        for (int i = 0; i < step; i++) {
            long s = (long) (vitualSize / Math.pow(10, i + 1));
            sb.append(lonL / s);
            sb.append(latL / s);
            lonL = lonL % s;
            latL = latL % s;
        }
        return DataUtil.toLong(sb.toString());
    }

    /**
     * 计算覆盖两个区域点的共同区域
     * 
     * @param locate1
     * @param locate2
     * @return
     */
    public long getSameLocation(long locate1, long locate2) {
        NumberFormat nf = new DecimalFormat("000000000000000000");
        StringBuffer sb = new StringBuffer();
        char[] ch1 = nf.format(locate1).toCharArray();
        char[] ch2 = nf.format(locate2).toCharArray();
        for (int i = 0; i < ch2.length; i++) {
            if (ch1[i] != ch2[i]) {
                break;
            }
            sb.append(ch1[i]);
        }
        return sb.toString().length();
    }

    /**
     * 根据两个坐标点，取得：[最小区域所属坐标系, 最小坐标区块, 最大坐标区块]
     * 
     * @param lon1
     *            第一点
     * @param lat1
     *            第一点
     * @param lon2
     *            第二点
     * @param lat2
     *            第二点
     * @return
     */
    public Object[] getLocationArea(double lon1, double lat1, double lon2, double lat2) {
        double lonMax = Math.max(lon1, lon2);
        double latMax = Math.max(lat1, lat2);
        double lonMin = Math.min(lon1, lon2);
        double latMin = Math.min(lat1, lat2);

        long loc1Min = getLocation1(lonMin, latMin);
        long loc1Max = getLocation1(lonMax, latMax);

        long loc2Min = getLocation2(lonMin, latMin);
        long loc2Max = getLocation2(lonMax, latMax);

        long loc3Min = getLocation3(lonMin, latMin);
        long loc3Max = getLocation3(lonMax, latMax);

        long same1 = getSameLocation(loc1Min, loc1Max);
        long same2 = getSameLocation(loc2Min, loc2Max);
        long same3 = getSameLocation(loc3Min, loc3Max);
        //System.out.println(same1 + " " + same2 + " " + same3);
        if (same1 >= same2 && same1 >= same3) {
            return new Object[] { 1, loc1Min, loc1Max };
        }
        if (same2 >= same1 && same2 >= same3) {
            return new Object[] { 2, loc2Min, loc2Max };
        }
        return new Object[] { 3, loc3Min, loc3Max };
    }

    /**
     * 取得默认的实例，适用于地球经纬度。
     * 
     * @return
     */
    public static Location2DUtil getDefault() {
        if (instance == null) {
            instance = new Location2DUtil(-180, -90, 180, 90, 9);
        }
        return instance;
    }

    //    private static void println(Object[] args) {
    //        System.out.println(args[0] + " " + args[1] + " " + args[2]);
    //    }

    private static final double EARTH_RADIUS = 6371229; // 地球的半径

    //    /**
    //     * 计算点（经纬度）之间的距离
    //     * 
    //     * @param lat1
    //     *            点1的经度
    //     * @param lon1
    //     *            点1的维度
    //     * @param lat2
    //     *            点2的经度
    //     * @param lon2
    //     *            点2的维度
    //     * @return
    //     */
    //    public static double getLocationDistance1(double lat1, double lon1, double lat2, double lon2) {
    //        double x, y, distance;
    //        x = (lon2 - lon1) * Math.PI * EARTH_RADIUS * Math.cos(((lat1 + lat2) / 2) * Math.PI / 180) / 180;
    //        y = (lat2 - lat1) * Math.PI * EARTH_RADIUS / 180;
    //        distance = Math.hypot(x, y);
    //        return distance;
    //    }

    /**
     * 根据经纬度点（lat1, lon1），计算距离distance之外的纬度范围
     * 
     * @param lat1
     *            点1的经度
     * @param lon1
     *            点1的维度
     * @param distance
     *            目标距离
     * @return
     */
    public static double getDistanceLon(double lat1, double lon1, double distance) {
        double a = (180 * distance) / (Math.PI * EARTH_RADIUS * Math.cos(lat1 * Math.PI / 180));
        return a;
    }

    /**
     * 根据经纬度点（lat1, lon1），计算距离distance之外的经度范围
     * 
     * @param lat1
     *            点1的经度
     * @param lon1
     *            点1的维度
     * @param distance
     *            目标距离
     * @return
     */
    public static double getDistanceLat(double lat1, double lon1, double distance) {
        double a = (180 * distance) / (Math.PI * EARTH_RADIUS * Math.cos(lat1 * Math.PI / 180));
        return a;
    }

    private static double rad(double d) {
        return d * Math.PI / 180.0;
    }

    /**
     * 计算两点（经纬度）之间距离
     * 
     * @param lat1
     *            点1的经度
     * @param lon1
     *            点1的维度
     * @param lat2
     *            点2的经度
     * @param lon2
     *            点2的维度
     * @return
     */
    public static double getLocationDistance(double lat1, double lon1, double lat2, double lon2) {
        double radLat1 = rad(lat1);
        double radLat2 = rad(lat2);
        double a = radLat1 - radLat2;
        double b = rad(lon1) - rad(lon2);
        double s = 2 * Math.asin(Math.sqrt(Math.pow(Math.sin(a / 2), 2) + Math.cos(radLat1) * Math.cos(radLat2)
                * Math.pow(Math.sin(b / 2), 2)));
        s = s * EARTH_RADIUS;
        //s = Math.round(s * 10000) / 10000;
        return s;
    }

    public static void main(String[] args) {
        System.out.println(getLocationDistance(12.131, 12.235, 12.131, 12.123));
        System.out.println(getDistanceLon(12.131, 12.123, 12176.1712));
        System.out.println(getDistanceLon(12.131, 12.235, 12176.1712));
        System.out.println(getLocationDistance(12.131, 12.235, 12.304, 12.235));
        System.out.println(getDistanceLat(12.304, 12.235, 19237.413756598322));
        System.out.println(getDistanceLat(12.131, 12.235, 19237.413756598322));
    }
}
